package controller;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

import dal.JMLDatabase;
import dal.datastore.TableData;
import dto.ColumnDTO;
import dto.RowDTO;

public class ClientController extends Thread {

	private Socket client;
	private DataInputStream dis;
	private DataOutputStream dos;

	/**
	 * Constructor
	 * 
	 * @param client
	 * @throws IOException
	 */
	public ClientController(Socket client) throws IOException {
		this.client = client;
		InputStream is = this.client.getInputStream();
		OutputStream os = this.client.getOutputStream();
		this.dis = new DataInputStream(is);
		this.dos = new DataOutputStream(os);
	}

	@Override
	public synchronized void start() {
		super.start();
	}

	/**
	 * check for login before start running for receiving requests and sending
	 * responses
	 * 
	 * @param username
	 *            string - username to check
	 * @param password
	 *            string - password to check
	 * @return null
	 * @author JML
	 * @throws IOException
	 */
	public boolean logIn() throws IOException {
		String username = this.dis.readUTF();
		String password = this.dis.readUTF();

		AuthenticationController authCtrl = new AuthenticationController(
				username, password);
		try {
			boolean result = authCtrl.isValidAuthenticationInfo();
			if (result) {
				this.dos.write(1);
				return true;
			} else {
				this.dos.write(0);
				return false;
			}
		} catch (Exception e) {
			this.dos.write(0);
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * receive client request and response here
	 * 
	 * @param null
	 * @return null
	 * @author JML
	 */
	@Override
	public void run() {
		// try {
		// while(!this.client.isClosed() && this.dis.available()!=0){
		while (!this.client.isClosed()) {
			try {
				int msgCode;
				if ((msgCode = this.dis.readInt()) != -1) {
					this.parseMessage(msgCode);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		// } catch (IOException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }

	}

	/**
	 * parse message code to do as request
	 * 
	 * @param null
	 * @return null
	 * @author JML
	 * @throws IOException
	 * @throws Exception
	 */
	public void parseMessage(int msgCode) throws IOException {
		JMLDatabase db = new JMLDatabase();
		Gson gson = new Gson();
		String dbName = "";
		String tableName = "";
		switch (msgCode) {
		case EMessageCode.login:
			this.logIn();
			break;
		case EMessageCode.logout:
			this.logout();
			break;
		case EMessageCode.createDatabase:
			try {
				dbName = this.dis.readUTF();
				boolean res = db.createDatabase(dbName);
				this.dos.writeBoolean(res);
				this.dos.flush();
			} catch (Exception e) {
				this.dos.writeBoolean(false);
				this.dos.writeUTF(e.getMessage());
				this.dos.flush();
				e.printStackTrace();
			}
			break;
		case EMessageCode.createTable:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			String json11 = this.dis.readUTF();
			JsonParser par = new JsonParser();
			JsonArray arr = par.parse(json11).getAsJsonArray();
			ArrayList<ColumnDTO> colArr21 = new ArrayList<ColumnDTO>();
			for (int i = 0; i < arr.size(); i++) {
				ColumnDTO col = gson.fromJson(arr.get(i), ColumnDTO.class);
				colArr21.add(col);
			}
			boolean res2;
			try {
				res2 = db.createTable(dbName, tableName, colArr21);
				this.dos.writeBoolean(res2);
				this.dos.flush();
			} catch (Exception e) {
				this.dos.writeBoolean(false);
				this.dos.writeUTF(e.getMessage());
				this.dos.flush();
				e.printStackTrace();
			}
			break;
		case EMessageCode.insertRow:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			String json = this.dis.readUTF();
			System.out.println(json);
			RowDTO rowData = gson.fromJson(json, RowDTO.class);
			try {
				boolean res20 = db.insertRow(dbName, tableName, rowData);
				this.dos.writeBoolean(res20);
				this.dos.flush();
			} catch (Exception e2) {
				// TODO Auto-generated catch block
				e2.printStackTrace();
			}
			break;
		case EMessageCode.insertRows:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			String json21 = this.dis.readUTF();
			JsonParser parser21 = new JsonParser();
			JsonArray array = parser21.parse(json21).getAsJsonArray();
			ArrayList<RowDTO> rows = new ArrayList<>();
			for (int i = 0; i <array.size(); i++) {
				RowDTO row = gson.fromJson(array.get(i), RowDTO.class);
				rows.add(row);
			}
			try {
				boolean res21 = db.insertRows(dbName, tableName, rows);
				this.dos.writeBoolean(res21);
				this.dos.flush();
			} catch (Exception e2) {
				// TODO Auto-generated catch block
				e2.printStackTrace();
			}
			break;
		case EMessageCode.replaceTableData:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			String json30 = this.dis.readUTF();
			TableData tblData = gson.fromJson(json30, TableData.class);
			boolean res30 = db.replaceTableData(dbName, tableName, tblData);
			this.dos.writeBoolean(res30);
			this.dos.flush();
			break;
		case EMessageCode.dropDatabase:
			dbName = this.dis.readUTF();
			boolean res;
			try {
				res = db.dropDatabase(dbName);
				this.dos.writeBoolean(res);
				this.dos.flush();
			} catch (Exception e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			break;
		case EMessageCode.dropTable:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			boolean res41;
			try {
				res41 = db.dropTable(dbName, tableName);
				this.dos.writeBoolean(res41);
				this.dos.flush();
			} catch (Exception e1) {
				this.dos.writeBoolean(false);
				this.dos.flush();
				e1.printStackTrace();
			}
			break;
		case EMessageCode.getAllDatabaseName:
			ArrayList<String> dbNameArr = db.getAllDatabaseName();
			String res3 = "[]";
			if (dbNameArr != null) {
				res3 = gson.toJson(dbNameArr);
			}
			this.dos.writeUTF(res3);
			this.dos.flush();
			break;
		case EMessageCode.getTableColumns:
			String dbname4 = this.dis.readUTF();
			String tablename4 = this.dis.readUTF();
			ArrayList<ColumnDTO> colArr;
			String res4 = "[]";
			try {
				colArr = db.getTableColumns(dbname4, tablename4);
				if (colArr != null) {
					res4 = gson.toJson(colArr);
				}
				this.dos.writeUTF(res4);
				this.dos.flush();
			} catch (Exception e) {
				this.dos.writeUTF(res4);
				this.dos.flush();
				e.printStackTrace();
			}
			break;
		case EMessageCode.getAllTableName:
			String dbname5 = this.dis.readUTF();
			ArrayList<String> tableNameArr;
			String res53 = "[]";
			try {
				tableNameArr = db.getAllTableName(dbname5);
				if (tableNameArr != null) {
					res53 = gson.toJson(tableNameArr);
				}
				this.dos.writeUTF(res53);
				this.dos.flush();
			} catch (Exception e) {
				this.dos.writeUTF(res53);
				this.dos.flush();
				e.printStackTrace();
			}
			break;
		case EMessageCode.select200Rows:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			ArrayList<RowDTO> res52 = null;
			String json52 = "[]";
			try {
				res52 = db.select200Rows(dbName, tableName);
				json52 = gson.toJson(res52);
			} catch (Exception e) {
				json52 = "[]";
			}

			this.dos.writeUTF(json52);
			this.dos.flush();
			break;
		case EMessageCode.getTableData:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();

			String json53 = "";
			try {
				TableData temp = db.getTableData(dbName, tableName);
				json53 = gson.toJson(temp);
			} catch (Exception e) {
				// json53 = "Error : " + e.getMessage();
			}
			this.dos.writeUTF(json53);
			this.dos.flush();
			break;
		case EMessageCode.update:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			
			String idxStr = this.dis.readUTF();
			String rowStr = this.dis.readUTF();
			
			ArrayList<Integer> indexes = new ArrayList<Integer>();
			rows = new ArrayList<RowDTO>();
			JsonParser parser = new JsonParser();
			JsonArray jsonArray   = parser.parse(idxStr).getAsJsonArray();

			for (JsonElement jsonElement : jsonArray) {
				indexes.add(gson.fromJson(jsonElement, Integer.class));
			}
			
			jsonArray = parser.parse(rowStr).getAsJsonArray();
			for (JsonElement jsonElement : jsonArray) {
				RowDTO row = gson.fromJson(jsonElement, RowDTO.class);
				rows.add(row);
			}
			
			boolean result = false;
			try {
				result = db.updateTableData(dbName, tableName, indexes, rows);
				this.dos.writeBoolean(result);
				this.dos.flush();
			} catch (Exception e) {
				this.dos.writeBoolean(result);
				this.dos.flush();
				e.printStackTrace();
			}
			break;
		case EMessageCode.delete:
			dbName = this.dis.readUTF();
			tableName = this.dis.readUTF();
			
			idxStr = "";
			idxStr = this.dis.readUTF();
			
			indexes = new ArrayList<Integer>();
			rows = new ArrayList<RowDTO>();
			parser = new JsonParser();
			jsonArray   = parser.parse(idxStr).getAsJsonArray();

			for (JsonElement jsonElement : jsonArray) {
				indexes.add(gson.fromJson(jsonElement, Integer.class));
			}
			
			result = false;
			try {
				result = db.deleteTableData(dbName, tableName, indexes);
				this.dos.writeBoolean(result);
				this.dos.flush();
			} catch (Exception e) {
				this.dos.writeBoolean(result);
				this.dos.flush();
				e.printStackTrace();
			}
			break;
		default:
			break;
		}
	}

	/**
	 * Logout
	 */
	private void logout() {
		try {
			this.dis.close();
			this.dos.close();
			this.client.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	protected void finalize() {
		try {
			this.dis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			this.dos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			this.client.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			super.finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
}
